-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local vrd_object = require 'vrd.vrd_object'
local skynet = require 'skynet'
local utils = require 'mc.utils'
local utils_core = require 'utils.core'
local file_sec = require 'utils.file'
local vrd_mgmt = require 'vrd.vrd_mgmt_obj'
local vrd_power_service = require 'vrd.chip.power.vrd_power_service'
local client = require 'general_hardware.client'
local cmn = require 'common'
local mdb = require 'mc.mdb'

local PMU_PATH<const> = '/bmc/kepler/Systems/%s/Pmu'
local PMU_INTERFACE<const> = 'bmc.kepler.Systems.Pmu'
local MULTIHOST_DEALY_TIME = 60 * 100
local SINGLEHOST_DEALY_TIME = 600
local DELAY_1_MINUTES = 6000
local DELAY_5_MINUTES = 5 * 60 * 100

local vrd_manager = {}
vrd_manager.__index = vrd_manager

function vrd_manager.new()
    return setmetatable({
        vrd_collection = {},
        vrd_mgmt_table = {},
        pmu_status_tb = {}
    }, vrd_manager)
end

function vrd_manager:add_vrd_object(obj, position)
    local v = vrd_object.new(obj, position)
    self.vrd_collection[#self.vrd_collection + 1] = v
end

function vrd_manager:on_add_object(class_name, object, position)
    local switch = {
        ['VrdMgmt'] = function()
            self:add_vrd_object(object, position)
            local v = vrd_mgmt.new(object, position)
            self.vrd_mgmt_table[#self.vrd_mgmt_table + 1] = v
            self.pmu_status_tb[object.SystemId] = 0
        end
    }

    if switch[class_name] then
        switch[class_name]()
        log:notice('[VrdMgmt] Add object, class: %s, position: %s', class_name, position)
    end
end


function vrd_manager:on_delete_object(class_name, object, position)
    local switch = {
        ['VrdMgmt'] = function()
            cmn.remove_ele_by_position(self.vrd_collection, position)
            cmn.remove_ele_by_position(self.vrd_mgmt_table, position)
        end
    }

    if switch[class_name] then
        switch[class_name]()
        log:notice('[VrdMgmt] Delete object, class: %s, position: %s', class_name, position)
    end
end

function vrd_manager:get_power_status(bus)
    for sys_id, _ in pairs(self.pmu_status_tb) do
        local ok, obj = pcall(mdb.get_object, bus, string.format(PMU_PATH, sys_id), PMU_INTERFACE)
        if ok and obj then
            self.pmu_status_tb[sys_id] = obj.Status
        end
    end
end

function vrd_manager:vrd_monitor_task(bus)
    pcall(function()
        self:get_power_status(bus)
    end
    )
    for sysid, power_state in pairs(self.pmu_status_tb) do
        -- 获取不到AC上电时重置属性
        pcall(function()
            if not power_state or power_state ~= 1 then
                self:reset_vrd_info(sysid)
            else
                self:update_vrd_info(bus,sysid)
            end
        end)
    end
end

local function is_multihost_type()
    local objs = client:GetMultihostObjects()
    if not objs then
        log:error('get Multihost Objects failed')
        return false
    end
    for _, o in pairs(objs) do
        -- 对象有且唯一，直接返回
        return o.HostType == 'Multihost'
    end
end

function vrd_manager:start_monitor_vrd(bus)
    local ok, err
    local host_type = is_multihost_type()
    skynet.sleep(host_type and DELAY_5_MINUTES or DELAY_1_MINUTES) -- 启动阶段等待一段时间再例测
    local delay_time = host_type and MULTIHOST_DEALY_TIME or SINGLEHOST_DEALY_TIME
    while true do
        skynet.sleep(delay_time)
        ok, err = pcall(self.vrd_monitor_task, self, bus)
        if not ok then
            log:debug('unable to get vrd info, error: %s', err)
        end
    end
end

function vrd_manager:reset_vrd_info(sys_id)
    -- 兼容旧的更新模式
    if not self.vrd_collection then
        return
    end
    pcall(function()
        for _, v in ipairs(self.vrd_collection) do
            if v.vrd.SystemId == sys_id then
                v:reset_vrd_info()
            end
        end
    end)

    if not self.vrd_mgmt_table or #self.vrd_mgmt_table == 0 then
        return
    end
    for _, v in ipairs(self.vrd_mgmt_table) do
        if v.mds_obj.SystemId == sys_id then
            v:reset_vrd_info()
        end
    end
end

-- 根据CPU物理ID查询vrd电压和温度
function vrd_manager:update_vrd_info(bus,sys_id)
    if not self.vrd_collection then
        return
    end
    pcall(function()
        for _, v in pairs(self.vrd_collection) do
            if v.vrd.SystemId == sys_id then
                v:update_vrd_info(bus)
            end
        end
    end)


    if not self.vrd_mgmt_table or #self.vrd_mgmt_table == 0 then
        return
    end
    for _, v in pairs(self.vrd_mgmt_table) do
        if v.mds_obj.SystemId == sys_id then
            v:update_vrd_info(bus)
        end
    end
end

function vrd_manager:on_dump_cb(ctx, path)
    if not path then
        log:error('path is nil')
        return
    end

    local print_data = ''
    local v_head = string.format(
        '%-10s | %-15s | %-15s | %-15s | %-15s | %-15s | %-15s\n',
        'SystemId', 'CpuId', 'Type', 'DieId', 'Voltage', 'CurrentAmps', 'TemperatureCelsius')
    print_data = print_data .. v_head
    local powers = vrd_power_service.get_instance().vrd_power_table
    for _, obj in pairs(powers) do
        local info = string.format(
            '%-10s | %-15s | %-15s | %-15s | %-15s | %-15s | %-15s\n',
            obj.mds_obj.SystemId, obj.mds_obj.CpuId, obj.mds_obj.Type, obj.mds_obj.DieId,
            obj.mds_obj.Voltage, obj.mds_obj.CurrentAmps, obj.mds_obj.TemperatureCelsius
        )
        print_data = print_data .. info
    end

    local file_name = path .. '/vrd_info'
    local file, err = file_sec.open_s(file_name, 'w+')
    if not file then
        log:error('open %s failed, err: %s', file_name, err)
        return
    end
    utils.close(file, pcall(file.write, file, print_data))
    utils_core.chmod_s(file_name, utils.S_IRUSR | utils.S_IWUSR | utils.S_IRGRP)
    log:notice('vrd info collect finish')
end

return singleton(vrd_manager)
